﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Threading;
using System.Drawing;

namespace SOMClustering
{
    public delegate void OnClusteringProgress(int iteration, Boolean end, double error, double dError);

    /// <summary>
    /// Самоорганизующаяся карта
    /// V. 1.0.0.0
    /// </summary>
    class SOM
    {
        Thread threadClustering;
        OnClusteringProgress OnClusteringEvent;

        // Параметры кластеризации
        Double eta0 = 0.1;
        Double sigma0 = 2.0;
        Double tau1 = 250;
        Double tau2 = 500;

        readonly int patternLength = 0;
        readonly int mapWidth = 0;
        readonly int mapHeight = 0;

        Double[,,] w;

        List<IPattern> inputSet;

        public SOM()
        {

        }

        public float this[int row, int col, int index]
        {
            get
            {
                return (float)w[row, col, index];
            }
        }

        //public Double this[int row, int col, int index]
        //{
        //    get
        //    {
        //        return w[row, col, index];
        //    }
        //}

        public int Width
        {
            get { return mapWidth; }
        }

        public int Height
        {
            get { return mapHeight; }
        }

        public int PatternLength
        {
            get { return patternLength; }
        }

        public SOM(int inputDim, int mapWidth, int mapHeight, Boolean patternInit, IPattern[] inputSet)
        {
            //tau1 = 1000 / (Math.Log10(eta0));

            this.patternLength = inputDim;
            this.mapHeight = mapHeight;
            this.mapWidth = mapWidth;

            w = new Double[mapHeight, mapWidth, inputDim];

            if (patternInit)
            {
                Initialize(inputSet);
            }
            else
            {
                Initialize();
            }
        }

        public SOM(int inputDim, int mapWidth, int mapHeight, Double[,,] w)
        {
            this.patternLength = inputDim;
            this.mapHeight = mapHeight;
            this.mapWidth = mapWidth;

            if (w.GetLength(0) == mapHeight &&
                w.GetLength(1) == mapWidth &&
                w.GetLength(2) == inputDim)
            {
                this.w = w;
            }
            else
            {
                w = new Double[mapHeight, mapWidth, inputDim];
                Initialize();
            }
        }

        Double eta(int n)
        {
            return eta0 * Math.Exp(-(double)n/tau2);
        }

        Double neighbourhood(int i, int j, int centerI, int centerJ, int n)
        {
            return Math.Exp(- ((i - centerI)*(i - centerI) + (j - centerJ) * (j - centerJ))/(2 * Math.Pow(sigma0 * Math.Exp(-n/tau1),2)));
        }

        void Initialize()
        {
            RandomW rnd = new RandomW();
            for (int i = 0; i < mapHeight; i++)
                for (int j = 0; j < mapWidth; j++)
                    for (int k = 0; k < patternLength; k++)
                        w[i, j, k] = rnd.NextW(0.1);
        }

        int Initialize(IPattern[] inputSet)
        {
            return 0;
        }
        
        public void Clustering(List<IPattern> inputSet, OnClusteringProgress onProgress)
        {
            ThreadStart threadStart = new ThreadStart(ClusteringProcess);
            threadClustering= new Thread(threadStart);

            OnClusteringEvent = onProgress;
            this.inputSet = inputSet;

            threadClustering.Start();
        }

        private void ClusteringProcess()
        {
            double error = 0.0;
            double e = double.MaxValue;
            double de = e - error;
            Boolean end = false;
            for (int i = 0; !end; i++)
            {
                error = ClusteringEpoch(inputSet, i);
                de = Math.Abs(e - error);
                if (de > 0.0001 && i < 10000) { }
                else
                    end = true;
                OnClusteringEvent.Invoke(i + 1, end, e, de);
                e = error;
            }

        }

        public double ClusteringEpoch(List<IPattern> inputSet, Int32 n)
        {
            double error = 0.0;

            int[] randOrder = new int[inputSet.Count];
            for (int i = 0; i < inputSet.Count; i++)
            {
                randOrder[i] = i;
            }

            // Подвыборка
            Random rnd = new Random(inputSet.Count);
            int placeToSwap;
            int temp;
            for (int i = 0; i < inputSet.Count; i++)
            {
                placeToSwap = rnd.Next(inputSet.Count);
                temp = randOrder[i];
                randOrder[i] = randOrder[placeToSwap];
                randOrder[placeToSwap] = temp;
            }

            for (int patIndex = 0; patIndex < inputSet.Count; patIndex++)
            {
                int currentPattern = randOrder[patIndex];
                // Поиск максимального подобия
                int winI = 0;
                int winJ = 0;
                Double min = Double.MaxValue;
                for (int i = 0; i < mapHeight; i++)
                    for (int j = 0; j < mapWidth; j++)
                    {
                        Double s = 0.0;
                        for (int k = 0; k < patternLength; k++)
                        {
                            s += Math.Abs(inputSet[currentPattern][k] - w[i, j, k]);
                        }
                        if (min > s)
                        {
                            min = s;
                            winI = i;
                            winJ = j;
                        }
                    }

                double e = 0.0;
                // Коррекция в соответствии с максимальным подобием
                for (int i = 0; i < mapHeight; i++)
                {
                    for (int j = 0; j < mapWidth; j++)
                    {
                        for (int k = 0; k < patternLength; k++)
                        {
                            e = neighbourhood(i, j, winI, winJ, n) * (inputSet[currentPattern][k] - w[i, j, k]);
                            w[i, j, k] = w[i, j, k] + eta(n) * e;
                            error += Math.Abs(e);
                        }
                    }
                }
            }

            return error;
        }

        public Point Coordinates(IPattern pattern)
        {
            int winI = 0;
            int winJ = 0;
            Double min = Double.MaxValue;
            for (int i = 0; i < mapHeight; i++)
                for (int j = 0; j < mapWidth; j++)
                {
                    Double s = 0.0;
                    for (int k = 0; k < patternLength; k++)
                    {
                        s += Math.Abs(pattern[k] - w[i, j, k]);
                    }
                    if (min > s)
                    {
                        min = s;
                        winI = i;
                        winJ = j;
                    }
                }
            return new Point(winJ, winI);
        }

        public SOM FromXml(XmlDocument somInfo)
        {
            Double eta0 = 0.1;
            Double sigma0 = 2.0;
            Double tau1 = 250;
            Double tau2 = 500;

            int patternLength = 0;
            int mapWidth = 0;
            int mapHeight = 0;
            Double[, ,] w;
            foreach (XmlNode node in somInfo.DocumentElement.ChildNodes)
            {
                switch (node.Name)
                {
                    case "patternLength":
                        patternLength = Int32.Parse(node.InnerText);
                        break;
                    case "mapWidth":
                        mapWidth = Int32.Parse(node.InnerText);
                        break;
                    case "mapHeight":
                        mapHeight = Int32.Parse(node.InnerText);
                        break;
                    case "eta0":
                        eta0 = Double.Parse(node.InnerText);
                        break;
                    case "sigma0":
                        sigma0 = Double.Parse(node.InnerText);
                        break;
                    case "tau1":
                        tau1 = Double.Parse(node.InnerText);
                        break;
                    case "tau2":
                        tau2 = Double.Parse(node.InnerText);
                        break;
                    default:
                        break;
                }
            }

            w = new Double[mapHeight, mapWidth, patternLength];

            foreach (XmlNode docNode in somInfo.DocumentElement.ChildNodes)
            {
                if (docNode.Name == "weights")
                {
                    int i, j, index;
                    foreach (XmlNode node in docNode.ChildNodes)
                    {
                        i = Int32.Parse(node.Attributes.GetNamedItem("i").Value);
                        j = Int32.Parse(node.Attributes.GetNamedItem("j").Value);
                        foreach (XmlNode nodeW in node.ChildNodes)
                        {
                            index = Int32.Parse(nodeW.Attributes.GetNamedItem("index").Value);
                            w[i, j, index] = Double.Parse(nodeW.InnerText);
                        }
                    }
                }
            }

            SOM map = new SOM(patternLength, mapWidth, mapHeight, w);
            return map;
        }

        public XmlDocument ToXml()
        {
            XmlDocument somInfo = new XmlDocument();
            somInfo.LoadXml("<?xml version=\"1.0\"?>" + "<SOM version=\"1.0\" creator=\"Cluster\"></SOM>");

            XmlNode node = somInfo.CreateElement("patternLength");
            node.InnerText = patternLength.ToString();
            somInfo.DocumentElement.AppendChild(node);

            node = somInfo.CreateElement("mapWidth");
            node.InnerText = mapWidth.ToString();
            somInfo.DocumentElement.AppendChild(node);

            node = somInfo.CreateElement("mapHeight");
            node.InnerText = mapHeight.ToString();
            somInfo.DocumentElement.AppendChild(node);

            node = somInfo.CreateElement("sigma0");
            node.InnerText = sigma0.ToString();
            somInfo.DocumentElement.AppendChild(node);

            node = somInfo.CreateElement("tau1");
            node.InnerText = tau1.ToString();
            somInfo.DocumentElement.AppendChild(node);

            node = somInfo.CreateElement("tau2");
            node.InnerText = tau2.ToString();
            somInfo.DocumentElement.AppendChild(node);

            XmlNode layerNode;
            XmlNode nodeWeight;

            XmlNode itemAttr;

            layerNode = somInfo.CreateElement("weights");

            for (int i = 0; i < mapHeight; i++)
            {
                for (int j = 0; j < mapWidth; j++)
                {
                    node = somInfo.CreateElement("node");

                    itemAttr = somInfo.CreateAttribute("i");
                    itemAttr.InnerText = i.ToString();
                    node.Attributes.Append((XmlAttribute)itemAttr);

                    itemAttr = somInfo.CreateAttribute("j");
                    itemAttr.InnerText = j.ToString();
                    node.Attributes.Append((XmlAttribute)itemAttr);

                    for (int k = 0; k < patternLength; k++)
                    {
                        nodeWeight = somInfo.CreateElement("w");

                        nodeWeight.InnerText = w[i, j, k].ToString();

                        itemAttr = somInfo.CreateAttribute("index");
                        itemAttr.InnerText = k.ToString();
                        nodeWeight.Attributes.Append((XmlAttribute)itemAttr);

                        node.AppendChild(nodeWeight);
                    }

                    layerNode.AppendChild(node);
                }
            }

            somInfo.DocumentElement.AppendChild(layerNode);

            return somInfo;
        }
    }
}